#!/usr/bin/env bash
#

# path to lua-compiler
DIR=`realpath $0`
DIR=`dirname $DIR`

CC=gcc
LIBTOOL="libtool --tag=CC --silent"
RPATH=`pwd`
LLVM_LUAC="./llvm-luac"
PREFIX="@CMAKE_INSTALL_PREFIX@"

# find llvm-luac
if [[ ! -x "$LLVM_LUAC" ]]; then
	LLVM_LUAC="$DIR/llvm-luac"
fi
if [[ ! -x "$LLVM_LUAC" ]]; then
	LLVM_LUAC=`which llvm-luac`
fi

TARGET=
FORCE_TARGET="0"
CPU=i686
#CPU=pentium4
#CPU=athlon64
FORCE_CPU="0"
FILE=
FILES=""
OUTPUT_FILE=""
DEBUG="0"
KEEP_TMPS="0"
STATIC="0"
MODE="full_bc"
EXPORT_SYMBOLS=" -Wl,-E "
LLC_FLAGS=" -relocation-model=pic "
EXTRA_ARGS=
LIBS=

function echo_cmd() {
	CMD="$1"
	shift 1
	echo "$CMD" "$@"
	$CMD "$@"
}

function version() {
	$LLVM_LUAC -version
	exit 0;
}

function usage() {
	echo "USAGE: $0 [options] <script>

OPTIONS:
  -c++             - Compile as C++ code instead of C code.  Requires that the core was
	                     also compiled as C++ code.
  -lua-module      - Compile Lua script into a loadable module instead of a standalone
                       executable.
  -debug           - Turns off all optimizations and turns on debug info.  Both Lua debug
                       info and gcc debug symbols are enabled.
  -keep-tmps       - Don't delete temp. files generated by intermediate stages.  Use only
                       for debuging generated code or if you are really curious!
  -target=<target> - <target> is passed to llc as '-target=<target>', see llc --version for targets
  -mcpu=<arch>     - <cpu> is passed to gcc as '-march=<cpu>'
  -mode=<mode>     - Switch how LLVM bitcode is generated and compiled into native code.
                   -   full_bc - (default mode) compiles Lua scripts into LLVM bitcode
                         linked with bitcode liblua_main.bc then converted to assembly
                         before being compiled with gcc to a native executable.
                   -   lua_mod - Only used by the '-lua-module' option.  Don't use
                         it directly.
                   The following modes are for testing only:
                   -   cbe - compile Lua script into C-code from LLVM bitcode, then use
                         gcc to compile to native executable binary.
                   -   c - compiles Lua scripts into C-code.
                   -   ll - compiles Lua scripts into LLVM IR code.
  -******          - All other options passed to 'llvm-luac'.  See below for a list of
                       options supported by 'llvm-luac'.

llvm-luac '-help' output:
"
	$LLVM_LUAC -help
	exit 0;
}

# parse command line parameters.
CONSUME=""
for arg in "$@" ; do
	case "$CONSUME" in
	-o)  OUTPUT_FILE=` echo "$arg" | sed -e 's/-o=//'` ;;
	-L)  EXTRA_ARGS="$EXTRA_ARGS -L $arg" ;;
	esac
	if [[ ! -z "$CONSUME" ]]; then
		CONSUME=""
		continue
	fi
	case "$arg" in
	-lua-mod*)  MODE="lua_mod"; EXTRA_ARGS="$EXTRA_ARGS $arg" ;;
	-static)  STATIC="1" ;;
	-c++)  CC=g++ ;;
	-debug)  DEBUG="1" ;;
	-keep-tmps)  KEEP_TMPS="1" ;;
	-mode=*)  MODE=` echo "$arg" | sed -e 's/-mode=//'` ;;
	-target=*)  FORCE_TARGET="1"; TARGET=` echo "$arg" | sed -e 's/-target=//'` ;;
	-mcpu=*)  FORCE_CPU="1"; CPU=` echo "$arg" | sed -e 's/-mcpu=//'` ;;
	-help|--help|-h)  usage ;;
	-version|--version|-v)  version ;;
	-o|-L)  CONSUME="$arg" ;;
	-L*)  EXTRA_ARGS="$EXTRA_ARGS $arg" ;;
	-*)  EXTRA_ARGS="$EXTRA_ARGS $arg" ;;
	*) FILE=${arg/.lua/}; FILES="$FILES $arg" ;;
	esac
done

# find the mode's output file extension.
OUTPUT_EXT=""
case "$MODE" in
	c) OUTPUT_EXT=".c" ;;
	ll) OUTPUT_EXT=".ll" ;;
	lua_mod) OUTPUT_EXT=".so" ;;
esac
if [[ -z "$OUTPUT_FILE" ]]; then
	OUTPUT_FILE="${FILE}${OUTPUT_EXT}"
else
	FILE=${OUTPUT_FILE}
fi

# get source file's path & filename.
FPATH=`dirname ${FILE}`
FNAME=`basename ${FILE}`
# strip extension from filename.
FILE="${FILE%\.*}"

# select debug/optimize parameters.
if [[ $DEBUG == "1" ]]; then
	CFLAGS=" -O0 -ggdb "
	LUA_FLAGS=" -O0 -g "
	#LUA_FLAGS=" -O3 -do-not-inline-opcodes "
	#CFLAGS=" -ggdb -O3 -fomit-frame-pointer -pipe -Wall "
	OPT_FLAGS=" -disable-opt "
	if [[ ! -z $CPU && $FORCE_CPU == "1" ]]; then
		CFLAGS=" -march=$CPU $CFLAGS "
	fi
else
	LUA_FLAGS=" -O3 -s "
	#LUA_FLAGS=" -O3 -g "
	#CFLAGS=" -ggdb -O3 -fomit-frame-pointer -pipe -Wall "
	CFLAGS=" -O3 -fomit-frame-pointer -pipe "
	if [[ ! -z $CPU ]]; then
		CFLAGS=" -march=$CPU $CFLAGS "
	fi
	OPT_FLAGS=" -O3 -std-compile-opts -tailcallelim "
	LLC_FLAGS=" -tailcallopt $LLC_FLAGS "
fi
if [[ ! -z $CPU && $FORCE_CPU == "1" ]]; then
	LLC_FLAGS=" -mcpu=$CPU $LLC_FLAGS "
fi

if [[ $MODE == "cbe" ]]; then
	EXTRA_ARGS="$EXTRA_ARGS -no-main "

	# path to liblua_main.a
	LIBS="$LIBS -L$DIR -L$DIR/../lib -L$PREFIX/lib "
elif [[ $MODE == "c" ]]; then
	EXTRA_ARGS="$EXTRA_ARGS -no-main "
else
	# if mode not "cbe" or "c"
	if [[ ! -z $TARGET && $FORCE_TARGET == "1" ]]; then
		LLC_FLAGS=" -march=$TARGET $LLC_FLAGS "
	fi
fi

#
# run llvm-luac to compile Lua source/bytecode to LLVM bitcode.
#
echo_cmd $LLVM_LUAC $EXTRA_ARGS $LUA_FLAGS -bc -o ${FILE}.bc ${FILES} || {
	echo "llvm-luac: failed to compile Lua code into LLVM bitcode."
	exit 1;
}
TMPS="${FILE}.bc"

# use one of the compile modes.
case "$MODE" in
	cbe)
		TMPS="$TMPS ${FILE}_opt.bc ${FILE}_run.c"
		echo_cmd opt $OPT_FLAGS -o ${FILE}_opt.bc ${FILE}.bc
		echo_cmd llc $LLC_FLAGS --march=c -o ${FILE}_run.c ${FILE}_opt.bc
		echo_cmd $CC $EXPORT_SYMBOLS $CFLAGS $LIBS -o ${OUTPUT_FILE} ${FILE}_run.c -lllvm-lua_main -lm -ldl
		;;
	c)
		TMPS="$TMPS ${FILE}_opt.bc"
		echo_cmd opt $OPT_FLAGS -o ${FILE}_opt.bc ${FILE}.bc
		echo_cmd llc $LLC_FLAGS --march=c -o ${OUTPUT_FILE} ${FILE}_opt.bc
		;;
	ll)
		echo_cmd llvm-dis -o ${OUTPUT_FILE} ${FILE}.bc
		;;
	full_bc)
		TMPS="$TMPS ${FILE}_opt.bc ${FILE}_run.bc ${FILE}_run.s"
		echo_cmd opt $OPT_FLAGS -o ${FILE}_run.bc ${FILE}.bc
		echo_cmd llc $LLC_FLAGS -filetype=asm -o ${FILE}_run.s ${FILE}_run.bc
		echo_cmd $CC $EXPORT_SYMBOLS $CFLAGS -o ${OUTPUT_FILE} ${FILE}_run.s -lm -ldl
		;;
	lua_mod)
		TMPS="$TMPS ${FILE}_opt.bc ${FILE}_mod.s ${FPATH}/${FNAME}.lo ${FPATH}/lib${FNAME}.la"
		echo_cmd opt $OPT_FLAGS -o ${FILE}_opt.bc ${FILE}.bc
		echo_cmd llc $LLC_FLAGS -filetype=asm -o ${FILE}_mod.s ${FILE}_opt.bc || {
			echo "Error compiling LLVM bitcode to assembly code."
			exit 1;
		}
		# compile assembly code to object file.
		$LIBTOOL --mode=compile $CC $CFLAGS -c -o ${FILE}.lo ${FILE}_mod.s
		# compile to dynamic module
		if [[ $STATIC == "0" ]]; then
			$LIBTOOL --mode=link $CC -rpath ${RPATH} -o ${FPATH}/lib${FNAME}.la ${FILE}.lo && \
			cp -p ${FPATH}/.libs/lib${FNAME}.so ${RPATH}/${OUTPUT_FILE}
			if [[ $KEEP_TMPS == "0" ]]; then
				$LIBTOOL --mode=clean rm -f $TMPS
			fi
		fi
		;;
	*)
		echo "Invalid compile mode: $MODE"
		;;
esac

if [[ $KEEP_TMPS == "0" ]]; then
	rm -f $TMPS
fi

